package biz.innovasoft.nodb.interceptors;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import net.sf.cglib.proxy.InvocationHandler;

import org.hamcrest.Matcher;

import biz.innovasoft.nodb.DeleteValidator;
import biz.innovasoft.nodb.Repository;
import biz.innovasoft.nodb.RepositoryCore;
import biz.innovasoft.nodb.RepositoryException;
import biz.innovasoft.nodb.RepositoryType;
import biz.innovasoft.nodb.StoreValidator;
import biz.innovasoft.nodb.UpdateValidator;
import biz.innovasoft.nodb.annotations.Query;
import biz.innovasoft.nodb.annotations.Transaction;
import biz.innovasoft.nodb.annotations.TransactionWithQuery;
import biz.innovasoft.nodb.index.DateResolution;
import biz.innovasoft.nodb.query.QueryCriteria;
import biz.innovasoft.nodb.query.QueryCriteriaImpl;
import biz.innovasoft.nodb.query.QueryType;
import biz.innovasoft.nodb.transaction.AddDeleteValidatorTransaction;
import biz.innovasoft.nodb.transaction.AddStoreValidatorTransaction;
import biz.innovasoft.nodb.transaction.AddUpdateValidatorTransaction;
import biz.innovasoft.nodb.transaction.AnnotatedQuery;
import biz.innovasoft.nodb.transaction.AnnotatedTransaction;
import biz.innovasoft.nodb.transaction.AnnotatedTransactionWithQuery;
import biz.innovasoft.nodb.transaction.CreateIndexTransaction;
import biz.innovasoft.nodb.transaction.DeleteTransaction;
import biz.innovasoft.nodb.transaction.GetAllQuery;
import biz.innovasoft.nodb.transaction.GetAllQueryMatcher;
import biz.innovasoft.nodb.transaction.GetByOidQuery;
import biz.innovasoft.nodb.transaction.GetObjectsByOidQuery;
import biz.innovasoft.nodb.transaction.GetSizeByClassQuery;
import biz.innovasoft.nodb.transaction.GetSizeQuery;
import biz.innovasoft.nodb.transaction.ReindexTransaction;
import biz.innovasoft.nodb.transaction.RemoveDeleteValidatorTransaction;
import biz.innovasoft.nodb.transaction.RemoveIndexTransaction;
import biz.innovasoft.nodb.transaction.RemoveStoreValidatorTransaction;
import biz.innovasoft.nodb.transaction.RemoveUpdateValidatorTransaction;
import biz.innovasoft.nodb.transaction.StoreTransaction;
import biz.innovasoft.nodb.transaction.UpdateTransaction;

public class BaseInterceptor implements InvocationHandler {

	protected RepositoryCore core;
	
	public BaseInterceptor () {
	}
	
	public void setRepositoryCore (RepositoryCore core) {
		this.core = core;
	}
	
	@SuppressWarnings("unchecked")
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {

		/*
		 * Get By Oid.
		 */
		if (method.getName ().equals ("getByOId")) {
			return core.getPrevayler().execute ( new GetByOidQuery(args[0]) );
		}
		
		/*
		 * Get by Oids.
		 */
		if (method.getName ().equals ("getObjectsByOId")) {
			return core.getPrevayler().execute ( new GetObjectsByOidQuery (args[0]));
		}
		
		/*
		 * GetAll methods. 
		 */
		if (method.getName().equals ("getAll")) {
			/* getAll () */
			if (args.length == 0) {
				return core.getPrevayler().execute(new GetAllQuery (core.getPrevayler()));
			}
			/* getAll(Class) */
			if (args.length == 1) {
				return core.getPrevayler().execute(new GetAllQueryMatcher(core.getPrevayler(), (Class<?>)args[0], null, false));
			}
			/* getAll(Class, Matcher) */
			if (args.length == 2) {
				return core.getPrevayler().execute (new GetAllQueryMatcher(core.getPrevayler(), (Class<?>)args[0], (Matcher)args[1], false));
			}
		}
		
		/*
		 * Size methods.
		 */
		if (method.getName().equals ("size")) {
			/* size() */
			if (args.length == 0) {
				return core.getPrevayler().execute(new GetSizeQuery());
			}
			/* size(Class) */
			if (args.length == 1) {
				return core.getPrevayler().execute(new GetSizeByClassQuery((Class<?>)args[0], false));
			}
			/* size(Class, boolean) */
			if (args.length == 2) {
				return core.getPrevayler().execute(new GetSizeByClassQuery((Class<?>)args[0], (Boolean)args[1]));
			}
		}
		
		/*
		 * Store methods.
		 */
		if (method.getName().equals("store")) {
			if (args.length == 1) {
				return core.getPrevayler().execute(new StoreTransaction (args[0], Repository.INDEX_YES));
			}
			if (args.length == 2) {
				return core.getPrevayler().execute(new StoreTransaction (args[0], (Integer)args[1]));
			}
		}
		
		/*
		 * Update methods.
		 */
		if (method.getName().equals ("update")) {
			if (args.length == 2) {
				return core.getPrevayler().execute (new UpdateTransaction ((Long)args[0], args[1], Repository.INDEX_YES));
			} else if (args.length == 3) {
				return core.getPrevayler().execute (new UpdateTransaction ((Long)args[0], args[1], (Integer)args[2]));
			}
		}
		
		/*
		 * Delete methods.
		 */
		if (method.getName().equals ("delete")) {
			core.getPrevayler().execute( new DeleteTransaction (args[0]));
			return null;
		}
		
		/*
		 * Reindex methods.
		 */
		if (method.getName().equals ("reindex")) {
			if (args.length == 0) {
				core.getPrevayler().execute (new ReindexTransaction());
			} else if (args.length == 1) {
				core.getPrevayler().execute (new ReindexTransaction (args[0]));
			}
			return null;
		}
		
		/*
		 * Remove Index methods
		 */
		if (method.getName().equals ("removeIndex")) {
			if (args.length == 1) {
				core.getPrevayler().execute(new RemoveIndexTransaction ((Class<?>)args[0]));
			} else if (args.length == 2) {
				core.getPrevayler().execute(new RemoveIndexTransaction ((Class<?>)args[0], (String)args[1]));
			}
			return null;
		}
		
		/*
		 * Create Index methods.
		 */
		if (method.getName().equals("createIndex")) {
			if (args.length == 2) {
				core.getPrevayler().execute(new CreateIndexTransaction ((Class<?>)args[0], (String)args[1]));
			} else if (args.length == 3) {
				if (args[3] instanceof DateResolution) {
					core.getPrevayler().execute(new CreateIndexTransaction((Class<?>)args[0], (String)args[1], (DateResolution)args[2]));
				} else {
					core.getPrevayler().execute(new CreateIndexTransaction((Class<?>)args[0], (String)args[1], (Boolean)args[2]));
				}
			}
			return null;
		}
		
		/*
		 * Validators
		 */
		if (method.getName().equals ("addStoreValidator")) {
			core.getPrevayler().execute (new AddStoreValidatorTransaction((String)args[0],
					(Class<?>)args[1], (StoreValidator)args[2]));
			return null;
		}

		if (method.getName().equals ("addUpdateValidator")) {
			core.getPrevayler().execute (new AddUpdateValidatorTransaction((String)args[0],
					(Class<?>)args[1], (UpdateValidator)args[2]));
			return null;
		}

		if (method.getName().equals ("addDeleteValidator")) {
			core.getPrevayler().execute (new AddDeleteValidatorTransaction((String)args[0],
					(Class<?>)args[1], (DeleteValidator)args[2]));
			return null;
		}

		if (method.getName().equals ("removeStoreValidator")) {
			core.getPrevayler ().execute ( new RemoveStoreValidatorTransaction (
					(String)args[0], (Class<?>)args[1]));
			return null;
		}

		if (method.getName().equals ("removeUpdateValidator")) {
			core.getPrevayler ().execute ( new RemoveUpdateValidatorTransaction (
					(String)args[0], (Class<?>)args[1]));
			return null;
		}

		if (method.getName().equals ("removeDeleteValidator")) {
			core.getPrevayler ().execute ( new RemoveDeleteValidatorTransaction (
					(String)args[0], (Class<?>)args[1]));
			return null;
		}

		
		/*
		 * Execute transactions and return values.
		 */
		if (method.isAnnotationPresent(TransactionWithQuery.class)) {
			try {
				Class<?> types[] = method.getParameterTypes();
				return core.getPrevayler().execute(new AnnotatedTransactionWithQuery(
						method.getName(), types, args));
			} catch (Exception e) {
				throw e.getCause();
			}
		}
		/*
		 * Excecute transactions
		 */
		if (method.isAnnotationPresent(Transaction.class)) {
			try {
				Class<?> types[] = method.getParameterTypes();
				core.getPrevayler().execute(new AnnotatedTransaction(method.getName(),
						types, args));
			} catch (Exception e) {
				if (e instanceof InvocationTargetException) {
					throw e.getCause();
				}
				throw e;
			}
			return null;
		}

		/*
		 * Execute queries against the repository.
		 */
		if (method.isAnnotationPresent(Query.class)) {
			try {
				if (core.getRepositoryConfiguration().getRepositoryType() == RepositoryType.MULTI_THREADED) {
					return executeNonPrevalentQuery(method, args);
				}
				Class<?> types[] = method.getParameterTypes();
				return core.getPrevayler().execute(new AnnotatedQuery(method.getName(),
						types, args));

			} catch (Exception e) {
				throw e.getCause();
			}
		}

		/*
		 * Create the criterias.
		 */
		if (method.getName().equals("createCriteria")) {
			if (args.length == 1) {
				return createCriteria((Class<?>) args[0], false,
						QueryType.PREVALENT);
			} else if (args.length == 2) {
				if (args[1] instanceof QueryType) {
					return createCriteria((Class<?>) args[0], false,
							(QueryType) args[1]);
				}
				return createCriteria((Class<?>) args[0], (Boolean) args[1],
						QueryType.PREVALENT);
			}
			return createCriteria((Class<?>) args[0], (Boolean) args[1],
					(QueryType) args[2]);
		}

		/*
		 * Take Snapshot of the repository.
		 */
		if (method.getName().equals("takeSnapshot")) {
			takeSnapshot();
			return null;
		}
		return method.invoke(core.getPrevalentSystem(), args);
	}

	/**
	 * Creates the criteria to search by index.
	 */
	private QueryCriteria createCriteria(Class<?> clazz, boolean isStricted,
			QueryType type) {
		return new QueryCriteriaImpl (clazz, core, isStricted);
	}

	private Object executeNonPrevalentQuery(Method method, Object[] args)
			throws Exception {
		Class<?> types[] = method.getParameterTypes();
		Class<?> copyTypes[] = getCopyTypes(types);
		Object[] copyArgs = getCopyArgs(args);
		return core.getPrevayler().execute(new AnnotatedQuery(method.getName(),
				copyTypes, copyArgs));
	}

	private Object[] getCopyArgs(Object[] args) {
		Object[] copyArgs = new Object[args.length + 1];
		for (int t = 0; t < args.length; t++) {
			copyArgs[t] = args[t];
		}
		copyArgs[args.length] = true;
		return copyArgs;
	}

	private Class<?>[] getCopyTypes(Class<?>[] types) {
		Class<?>[] copyTypes = new Class<?>[types.length + 1];
		for (int t = 0; t < types.length; t++) {
			copyTypes[t] = types[t];
		}
		copyTypes[types.length] = boolean.class;
		return copyTypes;
	}

	protected void close() throws RepositoryException {
		try {
			core.getPrevayler().close();
		} catch (IOException e) {
			throw new RepositoryException("Error closing the repository");
		} finally {
			core.getPrevalentSystem().close();
		}
	}

	public void takeSnapshot() throws RepositoryException {
		try {
			core.getPrevayler().takeSnapshot();
		} catch (Exception e) {
			throw new RepositoryException(
					"Error taking snapshot.", e);
		}
	}

}
